package org.kanstantin.nwscan.crawler;

import jcifs.UniAddress;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbAuthException;
import jcifs.smb.SmbException;
import jcifs.smb.SmbSession;
import org.apache.commons.cli.*;
import org.kanstantin.nwscan.crawler.network.AddressRange;
import org.kanstantin.nwscan.crawler.network.HostFinder;
import org.kanstantin.nwscan.crawler.network.IPBasedHostFinder;

import java.io.*;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;


/**
 * @author kanstantin
 *         <p/>
 *         if (hasOptionFile) {
 *         int flags = readOptionFile(file)
 *         }
 *         else if (hasCommandLineOptions) {
 *         int flags = readCommandLineOptions()
 *         }
 *         <p/>
 *         if (flags > 0 && console != null) {
 *         ... read missing parameters
 *         }
 *         <p/>
 *         if (checkAllOptions()) {
 *         exec()
 *         }
 *         else {
 *         print usage
 *         }
 *  flags
 *  0x00 - no options
 *  0x01 - domain name set
 *  0x02 - username set
 *  0x04 - password set
 *  0x08 - addressRange set
 */
public class CrawlerLauncher {
    protected String domain; // = cmd.hasOption("g") ? cmd.getOptionValue("g") : "";
    protected String username;// = cmd.getOptionValue("u");
    protected AddressRange addressRange;// = cmd.getOptionValue("N");
    protected String addressRangeAsString;
    protected String password;
    private Options options;
    protected UniAddress dc;
    protected File outputDir;

    protected final int FDOMAIN = 0x01;
    protected final int FUSERNAME = 0x02;
    protected final int FPASSWORD = 0x04;
    protected final int FSUBNET = 0x08;
    protected final int FNTPDC = 0x10;
    protected final int FOUTDIR = 0x20;

    public CrawlerLauncher() {
        options = new Options();
        options.addOption("g", true, "(NTLM Auth) Windows Domain name");
        options.addOption("u", true, "(NTLM Auth) Username");
        options.addOption("p", true, "(NTLM Auth) Password");
        options.addOption("n", true, "Subnet to be scanned");
        options.addOption("l", true, "Output directory");
        options.addOption("d", true, "(NTLM Auth) PDC");

    }

    public void start(String[] args) {
        boolean hasOptionFile = false;
        boolean hasCommandLineOptions = false;
        boolean hasToReadAddOptions = false;

        String optionFilename = System.getProperty("nwscanCrawlerOptFile");
        File optionFile = null;

        if (optionFilename != null) {
            optionFile = new File(optionFilename);
            if (optionFile.exists() && optionFile.canRead()) {
                hasOptionFile = true;
            }
        }
        if (args.length > 0) {
            hasCommandLineOptions = true;
        }

        int flags = 0;

        if (hasOptionFile) {

            InputStream is = null;
            try {
                is = new FileInputStream(optionFile);
            }
            catch(FileNotFoundException fex) {
                throw new RuntimeException(fex);
            }

            if (is != null) {
                flags = readOptionsFile(is);
            }

        }
        else if (hasCommandLineOptions) {
            flags = readCommandLineOptions(args);
        }

        Console con = System.console();

        if (flags < 0x3f) {
            readFromConsole(flags, con);
        }

        realLaunch(domain, username, password, addressRangeAsString);

    }

    protected void readFromConsole(final int flags, Console con) {

        if (con != null) {
            if ((flags & FNTPDC) == 0) {
                String tmp = con.readLine("%s ", "(NTLM Auth) PDC:");
                dc = stringToUniAddress(tmp);
            }

            if ((flags & FDOMAIN) == 0) {
                domain = con.readLine("%s ", "(NTLM Auth) Domain:");
            }

            if ((flags & FUSERNAME) == 0) {
                username = con.readLine("%s ", "(NTLM Auth) Username:");
            }

            if ((flags & FPASSWORD) == 0) {
                char[] passwd;
                passwd = con.readPassword("%s ", "(NTLM Auth) Password:");
                password = new String(passwd);
                Arrays.fill(passwd, ' ');
            }

            if ((flags & FSUBNET) == 0) {
                addressRangeAsString = con.readLine("%s ", "IP Subnet to scan:");
            }

            if ((flags & FOUTDIR) == 0) {
                outputDir = stringToDir(con.readLine("%s ", "output directory:"));
            }
        } else {
            throw new RuntimeException("Unable to get system console for input while there are undefined parameters");
        }

    }
    //TODO change the name
    protected File stringToDir(String fname) {
        File res = new File(fname);

        if (! (res.isDirectory() && res.canRead() && res.canWrite()) ){
            throw new RuntimeException("Check if directory " + fname + " exists and can be accessed for RW");
        }

        return res;


    }
    protected void printUsage() {
            HelpFormatter helpFormatter = new HelpFormatter();
            // TODO put correct POSIX program syntax
            helpFormatter.printHelp("programName {domain} [username] {password} [addressRange]", options);
    }

    protected void realLaunch(String domain, String username, String password, String addressRangeAsString) {
        NtlmPasswordAuthentication NtlmAuth = new NtlmPasswordAuthentication(domain, username, password);
        boolean auth = false;
        try {
            SmbSession.logon(dc, NtlmAuth);
            auth = true;
        }
        catch (SmbAuthException ex) {
            // authentication failure
        }
        catch (SmbException smbex) {
            throw new RuntimeException(smbex);
        }

        if ( ! auth ) {
            throw new RuntimeException("Authentication failure");
        }

        Map<String, Object> context = new HashMap<String, Object>();
        context.put("NTLMAUTH", NtlmAuth);
        addressRange = new AddressRange(addressRangeAsString);
        HostFinder hostFinder = new IPBasedHostFinder(addressRange);
        context.put("HOSTFINDER", hostFinder);
        context.put("OUTPUTDIR", outputDir);
        SambaScannerImpl scanner = new SambaScannerImpl(context);
        scanner.scanHosts();
    }

    protected int readOptionsFile(InputStream f) {
        int result = 0;

        Properties props = new Properties();
        try {
            props.load(f);
        }
        catch (FileNotFoundException fnex) {
            // I expect this should never occurs
            throw new RuntimeException("Internal error: unable to open file stream");
        }
        catch (IOException ioex) {
            throw new RuntimeException(ioex);
        }

        if (props.containsKey("NTPDC")) {
            result = result | FNTPDC;
            dc = stringToUniAddress(props.getProperty("NTPDC"));
        }

        if (props.containsKey("NTDOMAIN")) {
            result = result | FDOMAIN;
            domain = props.getProperty("NTDOMAIN");
        }

        if (props.containsKey("NTUSERNAME")) {
            result = result | FUSERNAME;
            username = props.getProperty("NTUSERNAME");
        }

        if (props.containsKey("NTPASSWORD")) {
            result = result | FPASSWORD;
            password = props.getProperty("NTPASSWORD");
        }

        if (props.containsKey("SUBNET")) {
            result = result | FSUBNET;
            addressRangeAsString = props.getProperty("SUBNET");
        }

        if (props.containsKey("OUTPUTDIR")) {
            result = result | FOUTDIR;
            outputDir = stringToDir(props.getProperty("OUTPUTDIR"));
        }

        return result;
    }

    protected int readCommandLineOptions(String[] args) {
        int result = 0;

        CommandLineParser parser = new PosixParser();
        CommandLine cmd;
        try {
            cmd = parser.parse(options, args);
        } catch (ParseException ex) {
            throw new RuntimeException(ex);
        }

        if (cmd == null) {
            throw new RuntimeException("Internal error: commandLine cannot be null");
        }

        if (cmd.hasOption("d")) {
            result = result | FNTPDC;
            dc = stringToUniAddress(cmd.getOptionValue("d"));
        }

        if (cmd.hasOption("g")) {
            result = result | FDOMAIN;
            domain =  cmd.getOptionValue("g");
        }

        if (cmd.hasOption("u")) {
            result = result | FUSERNAME;
            username = cmd.getOptionValue("u");
        }

        if (cmd.hasOption("p")) {
            result = result | FPASSWORD;
            password = cmd.getOptionValue("p");
        }

        if (cmd.hasOption("n")) {
            result = result | FSUBNET;
            addressRangeAsString = cmd.getOptionValue("n");
        }

        if (cmd.hasOption("l")) {
            result = result | FOUTDIR;
            outputDir = stringToDir(cmd.getOptionValue("l"));
        }

        return result;
    }

    private UniAddress stringToUniAddress(String tmp) {
        UniAddress res;
        try {
            res = UniAddress.getByName(tmp);
        }
        catch(UnknownHostException ex) {
            throw new RuntimeException(ex);
        }
        return res;
    }

    /*
        if (cmd.hasOption("u") && cmd.hasOption("n")) {
            domain = cmd.hasOption("g") ? cmd.getOptionValue("g") : "";

            addressRange = cmd.getOptionValue("N");

            if (cmd.hasOption("p")) {
                password = cmd.getOptionValue("p");
            } else {
                Console con = System.console();
                if (con == null) {
                    throw new RuntimeException("Failed to obtain system console; password cannot be get from input");
                } else {
                    char[] passwd;
                    int attempts = 0;
                    do {
                        passwd = con.readPassword("[%s]", "Password:");
                        ++attempts;
                    } while ((passwd == null) && attempts < 3);
                    password = new String(passwd);
                    Arrays.fill(passwd, ' ');
                }
            }
            internalLaunch(domain, username, password, addressRange);
        }
        */



}
